---
title: Lock
description: Lock 除了支持类似 synchronized 隐式加锁的 lock() 方法外，还支持可中断、超时、非阻塞的方式获取锁。
---


关于解决死锁问题的方案，对于破环不可抢占条件的方案，synchronized  是没有办法解决的。原因是 synchronized 申请资源的时候，如果申请不到，就会进入阻塞状态，而进入阻塞状态的线程，啥也干不了,也释放不了已经占有的资源。

:::tip 对于不可抢占条件，我们希望的是
占用部分资源的线程进一步申请其它资源时，如果申请不到，可以主动释放它占有的资源，这样不可抢占这个条件就被破环了。
:::

Lock 则可以全面弥补 synchronized 的问题。

1. **能够响应中断**。synchronized 的问题是，持有锁 A 后，如果尝试获取锁 B 失败，那么线程就进入阻塞状态，一旦发生死锁，就没有任何机会来唤醒阻塞的线程。但是如果阻塞状态的线程能够响应中断信号，也就相当于我们给阻塞的线程发送中断信号的时候，能够唤醒它，那它就有机会释放曾经持有的锁 A。这样就破环了不可抢占条件了。
2. **支持超时**。如果线程在一段时间之内没有获取到锁，不是进入阻塞状态，而是返回一个错误，那这个线程也有机会释放曾经持有的锁。这样也能破环不可抢占条件了。
3. **非阻塞地获取锁**。如果尝试获取锁，并不进入阻塞状态，而是直接返回。这样也能破环不可抢占条件。

体现在 API 上，就是 Lock 接口的三个方法。
```java
//支持中断的 API
void lockInterruptibly() throws InterruptedException;
//支持超时的 API
boolean tryLock(long time.TimeUnit unit) throws InterruptedException;
//支持非阻塞地获取锁的 API
boolean tryLock();
```
## 如何保证可见性

Java SDK 里面 Lock 的使用，有一个经典的范例，就是 try{}finally{},需要重点关注的就是 finally 里面释放锁。

Java 里多线程的可见性是通过 Happens-Before 规则保证的，那 Java SDK 里面的 Lock 靠什么保证可见性呢？

```java
class X {
    private final Lock rtl= new ReentrantLock();
    int value;

    public void addOne(){
        //获取锁
        rtl.lock()
        try{
            value += 1;
        }finally{
            //保证锁能释放
            rlt.unlock();
        }
    }
}
```

Java SDK 里面锁的实现非常复杂，它是利用了 volatile 相关的 Happens-Before 规则。Java SDK 里面的 ReentrantLock,内部持有一个 volatile 的成员变量 state，获取锁的时候，会读写 volatile 的值，释放锁的时候，也会读写 state 的值。
也就是说，在执行 value += 1 之前，程序先读写了一次 volatile 变量 state，在执行 value += 1 之后，又读写了一次 volatile 变量 state 。

```java title='Java SDK 锁简化后的代码'
class SampleLock{
    volatile int state;
    //加锁
    lock() {
        //省略无数代码
        state = 1;
    }
    //解锁
    unlock() {
        //省略无数代码
        state = 0;
    }
}

```
根据相关的 Happens—Before 规则：
1. **顺序性规则**
2. **volatile 变量规则**
3. **传递性规则**
所以后续线程 T2 能够看到 value 的正确结果。
## 可重入锁
**ReentrantLock 翻译过来叫做可重入锁。所谓可重入，故名思意就是线程可以重复获取同一把锁**

synchronized 也是可重入锁。
```java
class X {
    private final Lock rtl = new ReentrantLock();
    int value;
    public int get() {
        //获取锁
        rtl.lock();
        try {
            return value;
        }finally{
            //保证锁能释放
            trl.unlock();
        }
    }
    public void addOne(){
        rtl.lock();
        try {
            value = 1 + get();
        }finally{
            //保证锁能释放
            rtl.unlock();
        }
    }
}
```
如果锁是不可重入的，那么线程就会被阻塞。

## 公平锁和非公平锁
ReentrantLock 这个类有两个构造函数，一个是无参构造函数，一个是传入 fair 参数的构造函数。如果传入 true 表示要构造一个公平锁，反之则表示要构造一个非公平锁。

```java
//无参构造函数：默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//非公平锁
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() : new NonfairSync();
}
```
锁都对应着一个入口等待队列，如果一个线程没有获得锁，就会进入等待队列，当有线程释放锁的时候，就需要从等待队列中唤醒一个等待的线程。如果是公平锁，唤醒的策略就是谁等待的时间长，就唤醒谁，很公平；如果是非公平锁，则不提供公平性保证。
## 用锁的最佳实践
1. 永远只在更新对象的成员变量时加锁
2. 永远只在访问可变的成员变量时加锁
3. 永远不在调用其它对象的方法时加锁

